package measurement.network;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import wiiusej.WiiUseApiManager;
import wiiusej.Wiimote;
import wiiusej.apptest.statistics.SimResult;
import wiiusej.wiiusejevents.wiiuseapievents.WiiUseApiEvent;




public class NetworkSimulator implements Runnable {

	//Logger	
	private static Logger fileLogger;
	
	//EXECUTOR
	private static ExecutorService exe = null;

	//API controller
	private static WiiUseApiManager manager;
	
	//Wiimote connected devices
	private static HashMap<Integer, Wiimote> wiimotes;
	
	//COM queues
	private static BlockingQueue<WiiUseApiEvent> fromPostman;
	private static BlockingQueue<ArrayList<Long>> fromNetListener = 
		new LinkedBlockingQueue<ArrayList<Long>>(); 
	
	
	//Simulation parameters
	private static String[] simParameters;
	private static int simLength; //packets 
	private static short initTimeout; //milliseconds
	private static short finalTimeout; //milliseconds
	private static short timeoutIterations = 4; //no units
	private static short timeoutScale = 5; //milliseconds 
	

	//SimResults
	private static HashMap<Long, SimResult> mapResults = new HashMap<Long, SimResult>();
	private static WiimoteNetListener netListener;
	
	//Stats 
	private static HashMap<Long, Long[][]> results = new HashMap<Long, Long[][]>(); 
	
	private static HashMap<Short, ArrayList<Long>> delayStats = new HashMap<Short, ArrayList<Long>>();
	
	public NetworkSimulator(ExecutorService executor, WiiUseApiManager manager, BlockingQueue<WiiUseApiEvent> postmanToNetListener, 
			HashMap<Integer, Wiimote> wiimotes, String[] inputParameters){
		
		//executor framework
		this.exe = executor;
		this.manager = manager;
		
		//com queues
		this.fromPostman = postmanToNetListener;
		
		//connected devices
		this.wiimotes = wiimotes;
		
		this.simParameters = inputParameters;
		
		
		
		//Input parameters from Simulation
		this.simLength = Integer.parseInt(simParameters[1]);
		this.initTimeout = (short) Integer.parseInt(simParameters[2]);		
		this.finalTimeout = (short) (initTimeout + (timeoutIterations * timeoutScale));
		
		PropertyConfigurator.configure("log4j.properties");
		fileLogger = Logger.getLogger("NetworkSimulator");
		
	}
	
	public void run() {
		
		//Set up the connection
		setNetworkParameters(wiimotes);
		
		short currentTimeout = initTimeout;
		
		fileLogger.info("Init Timeout: " + initTimeout);
		fileLogger.info("");
		
		do{
			
			//ArrayList<Long> delayResults = new ArrayList<Long>();
			
			createNetListener();
			
			try {				
				ArrayList<Long> delayResults = new ArrayList<Long>();
				delayResults.addAll(fromNetListener.take()); 	
				delayStats.put(currentTimeout, delayResults);			
				
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}				
		
			//upload current timeout
			currentTimeout += timeoutScale;
			
			changeNetworkTimeout(wiimotes, currentTimeout);
			
			
		}while(finalTimeout >= currentTimeout);
		
		
		calculeStatistics();		
		printResults();
		
		System.out.println("\n SIMULATION FINISHED!!!");
		
	}
	
	private void createNetListener() {
		//fileLogger.info("Creating Network Listener");
		
		//Check if the last WiimoteNetListener is still running
		netListener = new WiimoteNetListener(manager, fromPostman, wiimotes, fromNetListener, 
				simParameters);
		
		exe.submit(netListener);
	}
	
	private void changeNetworkTimeout(HashMap<Integer, Wiimote> wiimotes, short timeout) {
		Iterator it = wiimotes.entrySet().iterator();
		while(it.hasNext()){
			Map.Entry entry = (Map.Entry) it.next();
			Wiimote aux = (Wiimote) entry.getValue();
			aux.setTimeout(timeout, timeout);							
		}		
	}
	
	private void setNetworkParameters(HashMap<Integer, Wiimote> wiimotes) {
		Iterator it = wiimotes.entrySet().iterator();
		
		fileLogger.info("Deactivating Motion/IR/Rumble/Continuous: ");		
		fileLogger.info("Init Timeout: " + initTimeout + " [ms]");
		
		while(it.hasNext()){
			Map.Entry entry = (Map.Entry) it.next();
			Wiimote aux = (Wiimote) entry.getValue();
			
			fileLogger.info("*** Wiimote: " + aux.getId());
			aux.setTimeout(initTimeout, initTimeout);
			aux.deactivateIRTRacking();
			aux.deactivateMotionSensing();
			aux.deactivateRumble();
			aux.deactivateContinuous();			
		}		
	}
	
	private void printResults(){
		
		fileLogger.info("");
		fileLogger.info("DATA: ");
		
		
		
		
		Iterator it = delayStats.entrySet().iterator();
		
		while(it.hasNext()){			
			Map.Entry entry = (Map.Entry) it.next();			
			ArrayList<Long> aux = (ArrayList<Long>) entry.getValue();
			
			fileLogger.info("");
			fileLogger.info("Timeout: " + entry.getKey());
			
			for(Long i : aux){
				fileLogger.info(i);
			}			
		}		
	}
	
	private void calculeStatistics(){
		
		fileLogger.info("STASTISTICS: ");
		DecimalFormat format = new DecimalFormat("#.##");
		
		Iterator it = delayStats.entrySet().iterator();
			
		while(it.hasNext()){
			
			Map.Entry entry = (Map.Entry) it.next();			
			ArrayList<Long> aux = (ArrayList<Long>) entry.getValue();
			
			//calculate average
			Double average = calculateAverage(aux);
			
			fileLogger.info("");
			fileLogger.info("Timeout: " + entry.getKey());
			fileLogger.info("Packets: " + aux.size());
			fileLogger.info("Average: " + format.format(average));
										
		}	
		
		
		
	}
	
	private Double calculateAverage(ArrayList<Long> delays){
		
		if(delays.size() > 0){
			Double result = new Double(0);
			
			for(Long aux : delays){
				result+=aux;
			}
			
			return result/delays.size();	
			
		}else {
			return null;
		}
			
		
		
	}
	

}
